﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Corecalc;
using System.Diagnostics;

namespace Sisal
{
    public enum SisalState { NotReferenced, Referenced }

    public abstract class SisalNode 
    {
        public static Dictionary<FullCellAddr, SisalNode> nodeEnvironment = new Dictionary<FullCellAddr, SisalNode>();

        public SisalNode ComplexAncestor { get; set; }        //Compound or Parallel ancestor
        public SisalNode FormulaNode { get; set; }       
        public SisalNode Parent { get; set; }
        public Task Task { get; set; }
        public Task TempTask { get; set; }
        public int Depth { get; set; }

        public List<SisalNode> InputEdges { get; set; }   
        public List<SisalNode> OutputEdges { get; set; }
        public List<SisalNode> Nodes { get; set; }          //Only non-null, if formula node

        public double Overhead { get; set; }
        public double CP { get; set; }

        public virtual double ExecutionTimeCost { get; set; }

        public int T_in { get; set; }
        public virtual int T_out { get; set; }

        public int F { get { return 1; } }      //Only set if IsFormulaNode is true
        public FullCellAddr Fca { get; set; }
        public bool IsFormulaNode { get; set; }
        public virtual SisalState State { get; set; }

        public int InpComCost {
            get { return InputEdges.Sum(e => e.OutComCost); }
        }

        public int OutComCost {
            get { return 0; }
        }

        public SisalNode() {
            InputEdges = new List<SisalNode>();
            OutputEdges = new List<SisalNode>();

            T_in = InputEdges.Sum(x => x.T_out);
        }

        //Returns the node in the graph represented by this node 
        //(should only be called on be the global root node)
        //Does not include sub graphs of compound- and parallel nodes

        public virtual List<SisalNode> GetGraph(bool subGraph) {
            List<SisalNode> ns = new List<SisalNode>();

            if (!ns.Contains(this)) {
                ns.Add(this);
            }

            List<SisalNode> edges = subGraph ? InputEdges : OutputEdges;
            foreach (SisalNode node in edges) {
                List<SisalNode> nss = node.GetGraph(subGraph);
                foreach (SisalNode node2 in nss) {
                    if (!ns.Contains(node2)) {
                        ns.Add(node2);
                    }
                }
            }

            foreach (SisalNode node in InputEdges) {
                if (node is UpToDateValueNode) {
                    ns.Add(node);
                }
            }

            return ns;
        }

        //Returns all nodes in the graph with this node as its root
        //Does NOT include subgraphs of compound and parallel nodes
        public virtual List<SisalNode> GetLocalGraph() {
            if (IsFormulaNode && Nodes != null) {
                return Nodes;
            } else if (IsFormulaNode && Nodes == null) {
                Nodes = GetLocalGraphNodes();
                return Nodes;
            } else {
                return GetLocalGraphNodes();
            }
        }

        private List<SisalNode> GetLocalGraphNodes() {
            List<SisalNode> ns = new List<SisalNode>();
            foreach (SisalNode inp in InputEdges) {
                if (!inp.IsFormulaNode && inp.FormulaNode == this.FormulaNode) {
                    ns.AddRange(inp.GetLocalGraphNodes());
                }
            }

            ns.Add(this);
            return ns;
        }

        public virtual void SetParent(SisalNode parent, SisalNode complexAncestor, bool @continue, List<FullCellAddr> path) {
            Parent = parent;
            ComplexAncestor = complexAncestor;

            //If the sender of a value (inputedge) is not on the path from the root to this node (via output edges), 
            //then we need to set its parent recursively using the input edges of this node
            foreach (SisalNode inputEdge in InputEdges) {
                if (!path.Contains(inputEdge.Fca) || Fca == inputEdge.Fca) {
                    inputEdge.SetParent(parent, complexAncestor, false, path);
                }
            }

            if (@continue) {
                foreach (SisalNode node in OutputEdges)
                    node.SetParent(parent, complexAncestor, true, path);
            }
        }

        public void SetFormulaNode(SisalNode formulaNode) {
            FormulaNode = formulaNode;

            foreach (SisalNode node in InputEdges)
                node.SetFormulaNode((node.IsFormulaNode ? node : formulaNode));
        }

        public bool DFS(Action<SisalNode> f) {
            f(this);
            foreach (SisalNode node in OutputEdges) {
                f(node);
            }
            foreach (SisalNode node in OutputEdges) {
                node.DFS(f);
            }
            return false;
        }

        public virtual bool IsValueNode { get { return false; } }

        public override string ToString() {
            return string.Format("[Loc: {0}, ParentType: {1}, Formula: {2}, Ft: {3}, Tin: {4}, Tout: {5}]: \t", 
                Fca.ca,
                Parent == this ? this.GetType().Name : Parent.GetType().Name,
                IsFormulaNode, 
                ExecutionTimeCost.ToString().Replace(",", "."), 
                T_in, 
                T_out);
        }
    }

    class SimpleNode : SisalNode
    {

    }

    class CompoundNode : SisalNode
    {
        public List<SisalNode> Graphs { get; set; }

        public CompoundNode() {
            Graphs = new List<SisalNode>();
        }

        //NB: Overridden in class IfNode (takes MAX of true / false)
        public virtual double ComputeExecutionTimeCost() {
            return Graphs.Sum(x => x.ExecutionTimeCost);
        }
    }

    // Represents genuine calls to built-in functions. 
    // Calls like "SUM(A1:B2)", "AVG(A1:B)" etc.
    class BuiltInFunctionCallNode : SisalNode
    {
        public string Name { get; set; }
        public SisalNode[] Args { get; set; }
        public int LastInputSize { get; set; }
        public bool Par { get; set; }

        public BuiltInFunctionCallNode(string name, SisalNode[] args) {
            Name = name;
            Args = args;
            LastInputSize = 0;

            foreach (SisalNode arg in args) {
                InputEdges.Add(arg);
                arg.OutputEdges.Add(this);
            }

            Debug.Assert(name.Equals("SUM"));

            T_in = args.Sum(arg => arg.T_out);
            T_out = CommunicationCosts.Get("double");   //Should depend on the concrete function that is called
        }

        public override string ToString() {
            return base.ToString() + string.Format("BuiltInFunctionCallNode [name: {0}]", Name);
        }
    }

    class ParallelNode : SisalNode
    {
        public double F { get; set; }
        public int Size { get; set; }
        public SisalNode Graph { get; set; }

        public ParallelNode(SisalNode g) {
            Graph = g;
            F = 1.0;
        }
    }

    class UpToDateValueNode : SimpleNode
    {
        public FullCellAddr Fca { get; set; }

        public UpToDateValueNode(FullCellAddr fca) {
            Fca = fca;
        }

        public override string ToString() {
            return string.Format("UpToDateValueNode [Fca: {0}]", Fca);
        }
    }

    class CellAreaNode : SimpleNode
    {
        public void ComputeCommunicationCosts() {
            T_out = T_in = InputEdges.Sum(ie => ie.T_out);
        }

        public override string ToString() {
            return base.ToString() + string.Format("CellAreaNode [InputEdges: {0}]", InputEdges.Count());
        }
    }

    class BinOperator : SimpleNode
    {
        public string Operator  { get; set; }
        public SisalNode Left { get; set; }
        public SisalNode Right { get; set; }

        public BinOperator(string binOp, SisalNode left, SisalNode right) {
            Operator = binOp;
            Left = left;
            Right = right;

            this.InputEdges.Add(left);
            this.InputEdges.Add(right);

            left.OutputEdges.Add(this);
            right.OutputEdges.Add(this);

            T_in = left.T_out + right.T_out;
            T_out = CommunicationCosts.Get("double");
            ExecutionTimeCost = ExecutionTimes.Get(binOp);
        }

        public override string ToString() {
            return base.ToString() + string.Format("BinOperator [{0}]", Operator);
        }
    }

    class IfNode : CompoundNode
    {
        public IfNode(SisalNode cond, SisalNode trueBranch, SisalNode falseBranch) {
            InputEdges.Add(cond);
            cond.OutputEdges.Add(this);

            Graphs.Add(trueBranch);
            Graphs.Add(falseBranch);

            T_in = cond.T_out;
            T_out = Math.Max(trueBranch.T_out, falseBranch.T_out);
        }

        public override double ComputeExecutionTimeCost() {
            double @true = Graphs[0].ExecutionTimeCost;
            double @false = Graphs[1].ExecutionTimeCost;

            return Math.Max(@true, @false);
        }

        public override void SetParent(SisalNode parent, SisalNode complexAncestor, bool @continue, List<FullCellAddr> path) {
            Parent = parent;
            ComplexAncestor = complexAncestor;

            foreach (SisalNode graph in Graphs) {
                graph.SetParent(graph, this, @continue, path);
            }
        }

        public override string ToString() {
            return base.ToString() + string.Format("IfNode: [InputEdges: {0}, OutputEdges: {1}]", InputEdges.Count, OutputEdges.Count);
        }
    }

    class ValueNode<T> : SimpleNode 
    {
        public T Value { get; set; }

        public ValueNode(T value, int comCost) {
            Value = value;

            T_in = 0;
            T_out = comCost;
            ExecutionTimeCost = ExecutionTimes.Get("Const");
        }

        public override string ToString() {
            return base.ToString() + string.Format("ValueNode [value: {0}]", Value.ToString());
        }

        public override bool IsValueNode {
            get { return true; }
        }
    }
}
